#include "OpenGLWidget.h"
#include "qpainter.h"

#include "qgl.h"
#include "qevent.h"
#include <math.h>
#include <sstream>
#include <limits.h>

using namespace std;

OpenGLWidget::OpenGLWidget(QWidget * parent)
    : QGLWidget(parent)
{
    m_selected_image_camera = -1;
    m_scale = 1.0f;

    m_origin = Vector3D(0, 0 ,0);
    p_point_cloud = NULL;
    p_camera_list = NULL;
    m_distance = 30.0f;
    m_rotation = Vector3D(0, 0, 0);

    setAutoFillBackground(false);
}

OpenGLWidget::~OpenGLWidget()
{

}

void OpenGLWidget::initializeGL()
{
    makeCurrent();
}

void OpenGLWidget::paintEvent(QPaintEvent * event)
{
    makeCurrent();

    processRendering();

    processOverpainting();
}

void OpenGLWidget::resizeGL(int width, int height)
{
    makeCurrent();
    m_screen_width = width;
    m_screen_height = height;
    glViewport(0, 0, width, height);
}

void OpenGLWidget::pushAttribute()
{
    glPushAttrib(GL_ALL_ATTRIB_BITS);
}

void OpenGLWidget::popAttribute()
{
    glPopAttrib();
}

void OpenGLWidget::updateProjection()
{
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    gluPerspective(45.0f, (float) m_screen_width / (float) m_screen_height, 0.001f, 20000.0f);

    //if ( m_selected_image_camera == -1 )
    //{

        gluLookAt(0, 0, m_distance, 0, 0, -10.0f, 0, 1, 0);


        glRotatef(m_rotation.getX(), 1.0f, 0.0f, 0.0f);
        glRotatef(m_rotation.getY(), 0.0f, 1.0f, 0.0f);
        glRotatef(m_rotation.getZ(), 0.0f, 0.0f, 1.0f);

        glTranslatef(-m_origin.getX(), -m_origin.getY(), -m_origin.getZ());

    //}
    /*
    else
    {
        m_image_camera = p_camera_list->at(m_selected_image_camera);
        Vector3D direction = m_image_camera.getCameraRotationVector().normalize();

        gluLookAt(m_image_camera.getCameraPosition().getX(),
                  m_image_camera.getCameraPosition().getY(),
                  m_image_camera.getCameraPosition().getZ() + m_distance,
                  m_origin.getX(), m_origin.getY(), m_origin.getZ(), 0, 1, 0);

        glRotatef(m_rotation.getX(), 1.0f, 0.0f, 0.0f);
        glRotatef(m_rotation.getY(), 0.0f, 1.0f, 0.0f);
        glRotatef(m_rotation.getZ(), 0.0f, 0.0f, 1.0f);
    }
    */

    glGetDoublev(GL_PROJECTION_MATRIX, m_perspective_matrix);

    glMatrixMode(GL_MODELVIEW);
}

void OpenGLWidget::updateOrigin()
{
    Vector3D temp;

    float min[3], max[3];

    min[0] = min[1] = min[2] =  1000000.0f;
    max[0] = max[1] = max[2] = -1000000.0f;

    if ( p_point_cloud )
    {
        m_origin = Vector3D(0, 0, 0);

        for(int i=0; i < p_point_cloud->getPointCount(); i++)
        {
            temp = p_point_cloud->getPoint(i);

            if (temp.getX() < min[0] ) min[0] = temp.getX();
            if (temp.getY() < min[1] ) min[1] = temp.getY();
            if (temp.getZ() < min[2] ) min[2] = temp.getZ();
            if (temp.getX() > max[0] ) max[0] = temp.getX();
            if (temp.getY() > max[1] ) max[1] = temp.getY();
            if (temp.getZ() > max[2] ) max[2] = temp.getZ();

            m_origin = m_origin + temp;
        }

        temp.setX(m_origin.getX() / p_point_cloud->getPointCount());
        temp.setY(m_origin.getY() / p_point_cloud->getPointCount());
        temp.setZ(m_origin.getZ() / p_point_cloud->getPointCount());

        m_origin = temp;
    }

    int i = 0;
    int j = 0;

    float range[3];

    range[0] = fabs(max[0] - min[0]);
    range[1] = fabs(max[1] - min[1]);
    range[2] = fabs(max[2] - min[2]);

    float max_range;

    max_range = (range[0] < range[1]) ? range[1] : range[0];
    max_range = (range[2] < max_range) ? max_range : range[2];

    //m_scale = 1.0f / (max_range / 20.0f);

}

void OpenGLWidget::beginRendering()
{
    pushAttribute();

    glShadeModel(GL_SMOOTH);
    glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);

    glDepthFunc(GL_LEQUAL);

    glClearColor(0.2f, 0.2f, 0.2f, 1.0f);

    glEnable(GL_DEPTH_TEST);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glEnable(GL_TEXTURE_2D);
}

void OpenGLWidget::processRendering()
{
    beginRendering();

    updateProjection();

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    glGetDoublev(GL_MODELVIEW_MATRIX, m_modelview_matrix);

    // rendering point cloud

    //glTranslatef(-m_origin.getX(), -m_origin.getY(), -m_origin.getZ());


    //glPointSize(3.0f);

    //glBegin(GL_POINT);
        //glVertex3f(m_origin.getX(), m_origin.getY(), m_origin.getZ());
        //glVertex3f(0, 0, 0);
    //glEnd();


    glPointSize(1.0f);


    /*
    glBegin(GL_LINES);

        glColor3f(1.0f, 0.0f, 0.0f);
        glVertex3f(-10000.0f, 0.0f, 0.0f);
        glVertex3f( 10000.0f, 0.0f, 0.0f);

        glColor3f(0.0f, 1.0f, 0.0f);
        glVertex3f(0.0f, -10000.0f, 0.0f);
        glVertex3f(0.0f,  10000.0f, 0.0f);

        glColor3f(0.0f, 0.0f, 1.0f);
        glVertex3f(0.0f, 0.0f, -10000.0f);
        glVertex3f(0.0f, 0.0f,  10000.0f);

    glEnd();
    */

    if ( p_point_cloud )
    {
        glPushMatrix();

        glScalef(m_scale, m_scale, m_scale);

        glPointSize(1.5f);
        //glColor3f(1.0f, 1.0f, 1.0f);
        glBegin(GL_POINTS);

        for(int i=0; i < p_point_cloud->getPointCount(); i++)
        {
            Point3D * pPoint = &(p_point_cloud->getPoint(i));

            glColor3f(pPoint->getRed(), pPoint->getGreen(), pPoint->getBlue());
            glVertex3d(pPoint->getX(), pPoint->getY(), pPoint->getZ());
        }

        glEnd();

        glPushMatrix();
    }

    // rendering cameras

    if ( p_camera_list )
    {
        Vector3D rotation;
        glPointSize(7.0f);
        glColor3f(0.3f, 0.8f, 0.8f);


        // rotate point is distance of (3,0,0)
        // target point is...

        for(int i=0; i < p_camera_list->size(); i++)
        {
            if ( i == m_selected_image_camera )
            {
                //glColor3f(1.0f, 1.0f, 1.0f);
            }
            else
            {
                //glColor3f(0.0f, 0.7f, 0.7f);
            }

            glBegin(GL_POINT);

                glVertex3f(p_camera_list->at(i).getCameraPosition().getX(),
                           p_camera_list->at(i).getCameraPosition().getY(),
                           p_camera_list->at(i).getCameraPosition().getZ());
            glEnd();

            /*
            glPushMatrix();

                glLoadIdentity();

                glTranslatef(p_camera_list->at(i).getCameraPosition().getX(),
                             p_camera_list->at(i).getCameraPosition().getY(),
                             p_camera_list->at(i).getCameraPosition().getZ());

                glRotatef(p_camera_list->at(i).getCameraRotationVector().getX(), -1.0f, 0.0f, 0.0f);
                glRotatef(p_camera_list->at(i).getCameraRotationVector().getY(), 0.0f, 1.0f, 0.0f);
                glRotatef(p_camera_list->at(i).getCameraRotationVector().getZ(), 0.0f, 0.0f, 1.0f);

                glBegin(GL_LINES);

                    if ( i == m_selected_image_camera )
                    {
                        glColor3f(1.0f, 1.0f, 1.0f);
                    }
                    else
                    {
                        glColor3f(0.0f, 0.7f, 0.7f);
                    }

                    glVertex3f(-0.1f, 0.0f, 0.0f);
                    glVertex3f(0.1f, 0.0f, 0.0f);

                    glVertex3f(0.0f, -0.1f, 0.0f);
                    glVertex3f(0.0f, 0.1f, 0.0f);

                    glVertex3f(0.0f, 0.0f, -0.1f);
                    glVertex3f(0.0f, 0.0f, 0.1f);

                glEnd();

            glPopMatrix();

            if ( i == m_selected_image_camera )
            {
                glBegin(GL_LINES);

                    glVertex3f(p_camera_list->at(i).getCameraPosition().getX(),
                               p_camera_list->at(i).getCameraPosition().getY(),
                               p_camera_list->at(i).getCameraPosition().getZ());

                    glVertex3f(m_origin.getX(),
                               m_origin.getY(),
                               m_origin.getZ());

                glEnd();
            }

            /*
            rotation = p_camera_list->at(i).getCameraDirection().normalize();

            glPushMatrix();


            glRotatef(rotation.getX(), 1.0f, 0.0f, 0.0f);
            glRotatef(rotation.getY(), 0.0f, 1.0f, 0.0f);
            glRotatef(rotation.getZ(), 0.0f, 0.0f, 1.0f);

            glBegin(GL_LINE_LOOP);

            glVertex3d(p_camera_list->at(i).getCameraPosition().getX() + -0.01f,
                       p_camera_list->at(i).getCameraPosition().getY() +  0.01f,
                       p_camera_list->at(i).getCameraPosition().getZ() + -0.1f);

            glVertex3d(p_camera_list->at(i).getCameraPosition().getX() + -0.01f,
                       p_camera_list->at(i).getCameraPosition().getY() + -0.01f,
                       p_camera_list->at(i).getCameraPosition().getZ() + -0.1f);

            glVertex3d(p_camera_list->at(i).getCameraPosition().getX() +  0.01f,
                       p_camera_list->at(i).getCameraPosition().getY() + -0.01f,
                       p_camera_list->at(i).getCameraPosition().getZ() + -0.1f);

            glVertex3d(p_camera_list->at(i).getCameraPosition().getX() +  0.01f,
                       p_camera_list->at(i).getCameraPosition().getY() +  0.01f,
                       p_camera_list->at(i).getCameraPosition().getZ() + -0.02f);

            glEnd();

            glBegin(GL_LINES);

            glVertex3d(p_camera_list->at(i).getCameraPosition().getX() + -0.01f,
                       p_camera_list->at(i).getCameraPosition().getY() +  0.01f,
                       p_camera_list->at(i).getCameraPosition().getZ() + -0.02f);

            glVertex3d(p_camera_list->at(i).getCameraPosition().getX(),
                       p_camera_list->at(i).getCameraPosition().getY(),
                       p_camera_list->at(i).getCameraPosition().getZ());

            glVertex3d(p_camera_list->at(i).getCameraPosition().getX() + -0.01f,
                       p_camera_list->at(i).getCameraPosition().getY() + -0.01f,
                       p_camera_list->at(i).getCameraPosition().getZ() + -0.02f);

            glVertex3d(p_camera_list->at(i).getCameraPosition().getX(),
                       p_camera_list->at(i).getCameraPosition().getY(),
                       p_camera_list->at(i).getCameraPosition().getZ());

            glVertex3d(p_camera_list->at(i).getCameraPosition().getX() +  0.01f,
                       p_camera_list->at(i).getCameraPosition().getY() + -0.01f,
                       p_camera_list->at(i).getCameraPosition().getZ() + -0.02f);

            glVertex3d(p_camera_list->at(i).getCameraPosition().getX(),
                       p_camera_list->at(i).getCameraPosition().getY(),
                       p_camera_list->at(i).getCameraPosition().getZ());

            glVertex3d(p_camera_list->at(i).getCameraPosition().getX() +  0.01f,
                       p_camera_list->at(i).getCameraPosition().getY() +  0.01f,
                       p_camera_list->at(i).getCameraPosition().getZ() + -0.02f);

            glVertex3d(p_camera_list->at(i).getCameraPosition().getX(),
                       p_camera_list->at(i).getCameraPosition().getY(),
                       p_camera_list->at(i).getCameraPosition().getZ());

            glEnd();

            glPopMatrix();
            */
        }
    }

    endRendering();
}

void OpenGLWidget::endRendering()
{
    popAttribute();
}

void OpenGLWidget::beginOverpainting()
{
    pushAttribute();
}

void OpenGLWidget::processOverpainting()
{
    beginOverpainting();

    if ( p_point_cloud && p_camera_list )
    {
        QString distance;
        QString rendering;
        QString cameraSetup;
        QString temp;

        stringstream ss0;

        ss0 << "Camera[";

        if ( m_selected_image_camera == -1 )
        {
            ss0 << "NONE]";
        }
        else
        {
            ss0 << m_selected_image_camera << "]"
                << " Rotation = ["
                << p_camera_list->at(m_selected_image_camera).getCameraRotationVector().getX() << ","
                << p_camera_list->at(m_selected_image_camera).getCameraRotationVector().getY() << ","
                << p_camera_list->at(m_selected_image_camera).getCameraRotationVector().getZ() << "]";
        }

        cameraSetup = QString::fromStdString(ss0.str());

        stringstream ss;

        ss << "Rotation [" << m_rotation.getX() << "," << m_rotation.getY() << "," << m_rotation.getZ()
           << "] " << "Distance [" << m_distance << "]";

        distance = QString::fromStdString(ss.str());

        stringstream ss2;

        ss2 << p_point_cloud->getPointCount() << " points and " << p_camera_list->size() << " views";

        rendering = QString::fromStdString(ss2.str());

        QPainter painter(this);

        painter.setPen(QColor(255, 255, 255));
        painter.drawText(10, 15, cameraSetup);
        painter.drawText(10, 35, distance);
        painter.drawText(10, 55, rendering);

        painter.end();

    }

    endOverpainting();
}

void OpenGLWidget::endOverpainting()
{
    popAttribute();
}

void OpenGLWidget::refresh()
{
    update();
}

void OpenGLWidget::mousePressEvent(QMouseEvent * event)
{

}

void OpenGLWidget::mouseReleaseEvent(QMouseEvent * event)
{

}

void OpenGLWidget::mouseDoubleClickEvent(QMouseEvent * event)
{

}

void OpenGLWidget::mouseMoveEvent(QMouseEvent * event)
{

}

void OpenGLWidget::enterEvent(QEvent * event)
{
    setMouseTracking(true);
    grabKeyboard();
    update();
}

void OpenGLWidget::leaveEvent(QEvent * event)
{
    setMouseTracking(false);
    releaseKeyboard();
    update();
}

void OpenGLWidget::wheelEvent(QWheelEvent * event)
{
    m_distance += ((event->delta() / 1000.0f) * (0.1 * m_distance));

    if ( m_distance < 0.0f ) m_distance = 0.0f;

    update();
}

void OpenGLWidget::keyPressEvent(QKeyEvent * event)
{
    int camera_rotation_step = 4.0f;
    int camera_translate_step = 1.0f;
    float rotStep = 1.0f;

    int key = event->key();


    if ( event->modifiers() & Qt::ShiftModifier )
    {
        /*
            switch(key)
            {
            case Qt::Key_W: // move forward

                    m_current_camera.rotateX(camera_rotation_step);
                    break;

            case Qt::Key_S: // move backward

                    m_current_camera.rotateX(-camera_rotation_step);
                    break;

            case Qt::Key_A:

                    m_current_camera.rotateY(camera_rotation_step);
                    break;

            case Qt::Key_D:

                    m_current_camera.rotateY(-camera_rotation_step);
                    break;

            case Qt::Key_Q:

                    m_current_camera.rotateZ(camera_rotation_step);
                    break;

            case Qt::Key_E:

                    m_current_camera.rotateZ(-camera_rotation_step);
                    break;
            }
         */
    }
    else
    {
        double rotY, rotX, rotZ;

            switch(key)
            {
            case Qt::Key_D: // move forward

                    rotY = m_rotation.getY() + rotStep;

                    if ( rotY > 360.0f ) rotY -= 360.0f;
                    else if ( rotY < 0.0f ) rotY += 360.0f;

                    m_rotation.setY(rotY);

                    //m_current_camera.forwardTranslate(-camera_translate_step);

                    break;

            case Qt::Key_A: // move backward

                    rotY = m_rotation.getY() - rotStep;

                    if ( rotY > 360.0f ) rotY -= 360.0f;
                    else if ( rotY < 0.0f ) rotY += 360.0f;

                    m_rotation.setY(rotY);

                    //m_current_camera.forwardTranslate(camera_translate_step);
                    break;

            case Qt::Key_W:

                rotX = m_rotation.getX() - rotStep;

                if ( rotX > 360.0f ) rotX -= 360.0f;
                else if ( rotX < 0.0f ) rotX += 360.0f;

                m_rotation.setX(rotX);

                    //m_current_camera.rightTranslate(-camera_translate_step);
                    break;

            case Qt::Key_S:

                rotX = m_rotation.getX() + rotStep;

                if ( rotX > 360.0f ) rotX -= 360.0f;
                else if ( rotX < 0.0f ) rotX += 360.0f;

                m_rotation.setX(rotX);

                    //m_current_camera.rightTranslate(camera_translate_step);
                    break;

            case Qt::Key_Q:

                rotZ = m_rotation.getZ() + rotStep;

                if ( rotZ > 360.0f ) rotZ -= 360.0f;
                else if ( rotZ < 0.0f ) rotZ += 360.0f;

                m_rotation.setZ(rotZ);

                    //m_current_camera.upTranslate(camera_translate_step);
                    break;

            case Qt::Key_E:

                rotZ = m_rotation.getZ() - rotStep;

                if ( rotZ > 360.0f ) rotZ -= 360.0f;
                else if ( rotZ < 0.0f ) rotZ += 360.0f;

                m_rotation.setZ(rotZ);
                    //m_current_camera.upTranslate(-camera_translate_step);
                    break;

            case Qt::Key_PageUp:

                /*
                    m_camera_index++;

                    if ( m_camera_index > p_scene->getCameraCount() )
                            m_camera_index = 0;

                    // copies geometry
                    m_current_camera.copyGeometryFrom(p_scene->getCamera(m_camera_index));
                */
                    break;
            }
    }

    update();
}

void OpenGLWidget::keyReleaseEvent(QKeyEvent * event)
{

}
